<?php

/**
 * 
 * 回调基础类
 * @author widyhu
 *
 */
require_once "WxPay.Data.php";
require_once "WxPay.Api.php";

class WxPayNotify extends WxPayNotifyReply {

    /**
     * 
     * 回调入口
     * @param bool $needSign  是否需要签名输出
     */
    final public function Handle($needSign = true) {
        $msg = "OK";
        //当返回false的时候，表示notify中调用NotifyCallBack回调失败获取签名校验失败，此时直接回复失败
        $result = WxpayApi::notify(array($this, 'NotifyCallBack'), $msg);
        if ($result == false) {
            $this->SetReturn_code("FAIL");
            $this->SetReturn_msg($msg);
            return $this->ReplyNotify(false);
        } else {
            //该分支在成功回调到NotifyCallBack方法，处理完成之后流程
            $this->SetReturn_code("SUCCESS");
            $this->SetReturn_msg("OK");
        }
        return $this->ReplyNotify($needSign);
    }

    /**
     * 
     * 回调方法入口，子类可重写该方法
     * 注意：
     * 1、微信回调超时时间为2s，建议用户使用异步处理流程，确认成功之后立刻回复微信服务器
     * 2、微信服务器在调用失败或者接到回包为非确认包的时候，会发起重试，需确保你的回调是可以重入
     * @param array $data 回调解释出的参数
     * @param string $msg 如果回调处理失败，可以将错误信息输出到该方法
     * @return true回调出来完成不需要继续回调，false回调处理未完成需要继续回调
     */
    public function NotifyProcess($data, &$msg) {
        //TODO 用户基础该类之后需要重写该方法，成功的时候返回true，失败返回false
        return true;
    }

    /**
     * 
     * notify回调方法，该方法中需要赋值需要输出的参数,不可重写
     * @param array $data
     * @return true回调出来完成不需要继续回调，false回调处理未完成需要继续回调
     */
    final public function NotifyCallBack($data) {
        $msg = "OK";
        $result = $this->NotifyProcess($data, $msg);

        if ($result == true) {
            $this->SetReturn_code("SUCCESS");
            $this->SetReturn_msg("OK");
        } else {
            $this->SetReturn_code("FAIL");
            $this->SetReturn_msg($msg);
        }
        return $result;
    }

    /**
     * 
     * 回复通知
     * @param bool $needSign 是否需要签名输出
     */
    final private function ReplyNotify($needSign = true) {
        //如果需要签名
        if ($needSign == true &&
                $this->GetReturn_code($return_code) == "SUCCESS") {
            $this->SetSign();
        }
        return WxpayApi::replyNotify($this->ToXml());
    }

    /**
     * 
     * @param type $openId
     * @param type $orderNo
     * @return type
     */
    public function unifiedorder($openId, $orderNo, $totalFee,$notify_url = "https://rpgminidev.gjgj.com/Pay/wxPayOrderCallBack",$body = "打赏红包") {
        //统一下单
        $input = new \WxPayUnifiedOrder();
        $input->SetBody($body);
        $input->SetOut_trade_no($orderNo);
        $input->SetTotal_fee($totalFee);
        $input->SetOpenid($openId);
        $input->SetNotify_url($notify_url);
        $input->SetTrade_type("JSAPI");
        $result = WxPayApi::unifiedOrder($input);
        return $result;
    }

    public function createPaySign($result) {
        $time = time();
        $tmp['appId'] = WxPayConfig::APPID;
        $tmp['nonceStr'] = $result['nonce_str'];
        $tmp['package'] = 'prepay_id=' . $result['prepay_id'];
        $tmp['signType'] = 'MD5';
        $tmp['timeStamp'] = "$time"; //时间戳
        $data = $tmp;
        $data['paySign'] = $this->sign($tmp); //签名,具体签名方案参见微信公众号支付帮助文档;
        return $data;
    }

    //签名 $data要先排好顺序
    private function sign($data) {
        $stringA = '';
        foreach ($data as $key => $value) {
            if (!$value)
                continue;
            if ($stringA)
                $stringA .= '&' . $key . "=" . $value;
            else
                $stringA = $key . "=" . $value;
        }
        $wx_key = WxPayConfig::KEY; //申请支付后有给予一个商户账号和密码，登陆后自己设置key
        $stringSignTemp = $stringA . '&key=' . $wx_key; //申请支付后有给予一个商户账号和密码，登陆后自己设置key    
        return strtoupper(md5($stringSignTemp));
    }

    /**
     * 以ssl证书的post方式提交到对应的接口url
     * 
     * @param string $url  url
     * @param string $aPOSTParam  需要post的数据
     * @param bool $useCert 是否需要证书，默认不需要
     * @param int $second   url执行超时时间，默认30s
     * @throws WxPayException
     */
    private static function postSSLCurl($url, $aPOSTParam, $aHeader, $second = 30) {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);

        //如果有配置代理这里就设置代理
        if (WxPayConfig::CURL_PROXY_HOST != "0.0.0.0" && WxPayConfig::CURL_PROXY_PORT != 0) {
            curl_setopt($ch, CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
            curl_setopt($ch, CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
        }
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); //严格校验
        //设置header
        if (count($aHeader) >= 1) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $aHeader);
        }
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //设置证书
        //使用证书：cert 与 key 分别属于两个.pem文件
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);

        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $aPOSTParam);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            throw new WxPayException("curl出错，错误码:" . $error);
        }
    }

    /**
     * 企业支付 
     * @param string $openid    用户openID 
     * @param string $trade_no  单号 
     * @param string $money     金额 
     * @param string $desc      描述 
     * @return string   XML 结构的字符串 
     */
    public function enterprisePayment($openId, $tradeNo, $money, $desc = "零钱提现") {
        $data = array(
            'mch_appid' => WxPayConfig::APPID,
            'mchid' => WxPayConfig::MCHID,
            'nonce_str' => WxPayApi::getNonceStr(32),
            //'device_info' => '1000',  
            'partner_trade_no' => $tradeNo, //商户订单号，需要唯一  
            'openid' => $openId,
            'check_name' => 'NO_CHECK', //OPTION_CHECK不强制校验真实姓名, FORCE_CHECK：强制 NO_CHECK：  
            //'re_user_name' => 'jorsh', //收款人用户姓名  
            'amount' => $money * 100, //付款金额单位为分  
            'desc' => $desc,
            'spbill_create_ip' => $this->serverIP(),
        );
        $tmp = $data;
        ksort($tmp);
        //生成签名  
        $data['sign'] = $this->sign($tmp);
        //构造XML数据  
        $xmldata = $this->arrayToXml($data);
        $url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';
        //发送post请求  
        $res = $this->postSSLCurl($url, $xmldata);
        //付款结果分析  
        $content = $this->xmlToArray($res);
        if (strval($content['return_code']) == 'FAIL' && strval($content['result_code']) == 'FAIL' && strval($content['err_code']) == 'SYSTEMERROR') {
            $res = $this->postSSLCurl($url, $xmldata);
            $content = $this->xmlToArray($res);
        }
        if (strval($content['result_code']) == 'FAIL') {
            E("服务器支付失败:" . strval($content['err_code']) . ':' . strval($content['err_code_des']));
        }
        if (strval($content['return_code']) == 'FAIL') {
            E(strval($content['return_msg']));
        }
        $resdata = array(
            'return_code' => strval($content['return_code']),
            'result_code' => strval($content['result_code']),
            'nonce_str' => strval($content['nonce_str']),
            'partner_trade_no' => strval($content['partner_trade_no']),
            'payment_no' => strval($content['payment_no']),
            'payment_time' => strval($content['payment_time']),
        );
        return $resdata;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
     * */
    public function arrayToXml($data) {
        if (!is_array($data) || count($data) <= 0) {
            throw new WxPayException("数组数据异常！");
        }
        $xml = "<xml>";
        foreach ($data as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
     * */
    public function xmlToArray($xml) {
        $dom = new \DOMDocument();
        $dom->loadXML($xml);
        $data = array();
        $data['return_code'] = $dom->getElementsByTagName("result_code")->item(0)->nodeValue;
        $data['result_code'] = $dom->getElementsByTagName("result_code")->item(0)->nodeValue;
        if ($data['result_code'] == "SUCCESS") {
            $data['mch_appid'] = $dom->getElementsByTagName("mch_appid")->item(0)->nodeValue;
            $data['mchid'] = $dom->getElementsByTagName("mchid")->item(0)->nodeValue;
            $data['return_msg'] = $dom->getElementsByTagName("return_msg")->item(0)->nodeValue;
            $data['device_info'] = $dom->getElementsByTagName("device_info")->item(0)->nodeValue;
            $data['nonce_str'] = $dom->getElementsByTagName("nonce_str")->item(0)->nodeValue;
            $data['partner_trade_no'] = $dom->getElementsByTagName("partner_trade_no")->item(0)->nodeValue;
            $data['payment_no'] = $dom->getElementsByTagName("payment_no")->item(0)->nodeValue;
            $data['payment_time'] = $dom->getElementsByTagName("payment_time")->item(0)->nodeValue;
        } else {
            $data['return_msg'] = $dom->getElementsByTagName("return_msg")->item(0)->nodeValue;
            $data['result_code'] = $dom->getElementsByTagName("result_code")->item(0)->nodeValue;
            $data['err_code'] = $dom->getElementsByTagName("err_code")->item(0)->nodeValue;
            $data['err_code_des'] = $dom->getElementsByTagName("err_code_des")->item(0)->nodeValue;
        }
        return $data;
    }

    function serverIP() {
        return gethostbyname($_SERVER['SERVER_NAME']);
    }

}
